package com.hd123.rumba.gwt.widget.client.grid;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.FocusImpl;

public class DxGrid extends Grid implements TableListener, KeyboardListener, ClickListener {
  ArrayList columnDefs = null;
  DxGridDataProvider provider = null;
  DxGridSummaryProvider summaryProvider = null;
  CellRenderer currentDisplayer = null;
  String currentDisplayerStyleName = null;
  CellEditor currentEditor = null;
  int headerRowCount = 1;
  int footerRowCount = 0;
  int blankRowCount = -1;
  int currentRow = -1;
  int currentCol = -1;
  boolean readOnly = true;
  boolean hasSummaryRow = false;
  String rowStyleName;
  String selectedRowStyleName;
  String summaryRowStyleName;
  boolean compact = false;
  public Event currentEvent;
  /**
   * key=grid column index, value=column model index
   */
  private HashMap columnIndexMap = new HashMap();

  public DxGrid(int rowCount, int columnCount) {
    super(rowCount, columnCount);
    columnDefs = new ArrayList();
    setStyleName("DxGrid");
    setCellSpacing(1);
    setCellPadding(0);
    setWidth("100%");
    setCompact(false);
    addTableListener(this);
    addKeyboardListener(this);
    sinkEvents(Event.ONDBLCLICK);
    sinkEvents(Event.ONKEYDOWN);
    sinkEvents(Event.ONKEYPRESS);
    sinkEvents(Event.ONKEYUP);
  }

  public int getVisibleColumnCount() {
    int count = 0;
    for (int col = 0; col < columnDefs.size(); ++col)
      if (getColumnDef(col).isVisible())
        ++count;
    return count;
  }

  /**
   * 给定grid界面上的列号, 返回列定义中的列号(也是provider的列号)
   * 
   * @param columnIndex
   * @return
   */
  public int getColumnModelIndex(int viewColumnIndex) {
    return ((Integer) columnIndexMap.get(new Integer(viewColumnIndex))).intValue();
  }

  private void buildColumnIndexMap() {
    columnIndexMap.clear();
    int vc = 0;
    for (int col = 0; col < columnDefs.size(); ++col) {
      if (getColumnDef(col).isVisible()) {
        columnIndexMap.put(new Integer(vc), new Integer(col));
        ++vc;
      }
    }
  }

  public void refresh() {
    clear();
    if (provider.getColumnCount() != columnDefs.size()) {
      Window.alert("数据源的列数和表格的列数不一致.");
      return;
    }
    int visibleColumnCount = getVisibleColumnCount();
    if (visibleColumnCount != getColumnCount()) {
      resizeColumns(visibleColumnCount);
    }
    buildColumnIndexMap();
    if (compact) {
      int rowCount = provider.getRowCount() + headerRowCount + footerRowCount;
      if (rowCount != this.getRowCount()) {
        resizeRows(rowCount);
      }
    } else {
      int rowCount = provider.getPageSize() + headerRowCount + footerRowCount;
      if (getBlankRowCount() >= 0) {
        rowCount = provider.getPageSize() + headerRowCount + footerRowCount + blankRowCount;
        resizeRows(rowCount);
      } else if (rowCount > this.getRowCount()) {
        resizeRows(rowCount);
      }
    }
    // header row
    if (!isCompact()) {
      int vc = 0;
      for (int col = 0; col < columnDefs.size(); ++col) {
        DxGridColumnDef cd = getColumnDef(col);
        if (!cd.isVisible())
          continue;
        if (cd.getHeaderWidget() == null)
          setText(0, vc, cd.getCaption());
        else
          setWidget(0, vc, cd.getHeaderWidget());
        getCellFormatter().setStyleName(0, vc, cd.getHeadStyleName());
        getColumnFormatter().setWidth(vc, cd.getWidth());
        vc++;
      }
    }
    // data row
    for (int row = headerRowCount; row < getRowCount() - footerRowCount; ++row) {
      getRowFormatter().setStyleName(row, rowStyleName);
      int vc = 0;
      for (int col = 0; col < columnDefs.size(); ++col) {
        DxGridColumnDef cd = getColumnDef(col);
        if (!cd.isVisible())
          continue;
        CellRendererFactory rf = getCellRendererFactory(col);
        String value = (row - headerRowCount) < provider.getRowCount() ? provider.getData(
            (row - headerRowCount), col) : "";
        rf.render(this, row, vc, value, cd.getWidth(), cd.isReadOnly(), false);
        vc++;
      }
    }
    // summary row
    if (hasSummaryRow) {
      if (summaryProvider == null) {
        summaryProvider = new DxGridColumnSummaryProvider(provider);
        for (int col = 0; col < columnDefs.size(); ++col) {
          DxGridColumnDef cd = getColumnDef(col);
          summaryProvider.setSummaryMethod(col, cd.getSummaryMethod());
        }
      }

      int row = getRowCount() - 1;
      getRowFormatter().setStyleName(row, summaryRowStyleName);
      StringRendererFactory rf = new StringRendererFactory();
      rf.setStyleName("DxGrid-Cell-Summary");
      rf.setHorizontalAlignment(StringRendererFactory.ALIGN_RIGHT);
      int vc = 0;
      for (int col = 0; col < columnDefs.size(); ++col) {
        DxGridColumnDef cd = getColumnDef(col);
        if (!cd.isVisible())
          continue;
        rf.render(this, row, vc, summaryProvider.getData(col), cd.getWidth(), true, false);
        vc++;
      }
    }

    if (!isReadOnly()) {
      int oldRow = currentRow, oldCol = currentCol;
      CellEditor oldEditor = currentEditor;
      currentRow = -1;
      currentCol = -1;
      currentEditor = null;
      if (oldEditor != null && oldRow < getRowCount() - footerRowCount && oldCol < getColumnCount())
        activateEditor(oldRow, oldCol);
    } else if (provider.getRowCount() > 0) {
      selectRow(headerRowCount);
    }

    fireDxGridChange(this);
  }

  public void refreshData() {
    int oldRowCount = getRowCount() - footerRowCount;
    if (compact) {
      int rowCount = provider.getRowCount() + headerRowCount + footerRowCount;
      if (rowCount != this.getRowCount()) {
        resizeRows(rowCount);
      }
    } else {
      int rowCount = provider.getPageSize() + headerRowCount + footerRowCount;
      if (getBlankRowCount() >= 0) {
        rowCount = rowCount + blankRowCount;
        resizeRows(rowCount);
      } else if (rowCount > this.getRowCount()) {
        resizeRows(rowCount);
      }
    }
    // data row
    for (int row = headerRowCount; row < getRowCount() - footerRowCount; ++row) {
      if (row < oldRowCount) {
        int vc = 0;
        for (int col = 0; col < columnDefs.size(); ++col) {
          DxGridColumnDef cd = getColumnDef(col);
          if (!cd.isVisible())
            continue;
          int providerRow = row - headerRowCount;
          String value = providerRow < provider.getRowCount() ? provider.getData(providerRow, col)
              : "";
          Widget w = getWidget(row, vc);
          if (w instanceof CellRenderer)
            ((CellRenderer) w).setValue(value);
          else if (w instanceof CellEditor)
            ((CellEditor) w).setValue(value);
          vc++;
        }
      } else {
        getRowFormatter().setStyleName(row, rowStyleName);
        int vc = 0;
        for (int col = 0; col < columnDefs.size(); ++col) {
          DxGridColumnDef cd = getColumnDef(col);
          if (!cd.isVisible())
            continue;
          CellRendererFactory rf = getCellRendererFactory(col);
          String value = (row - headerRowCount) < provider.getRowCount() ? provider.getData(
              (row - headerRowCount), col) : "";
          rf.render(this, row, vc, value, cd.getWidth(), cd.isReadOnly(), false);
          vc++;
        }
      }
    }
    // summary row
    if (hasSummaryRow) {
      int row = getRowCount() - 1;

      // in case grid row count increased
      StringRendererFactory rf = null;
      if (row >= oldRowCount + footerRowCount) {
        getRowFormatter().setStyleName(row, summaryRowStyleName);
        rf = new StringRendererFactory();
        rf.setStyleName("DxGrid-Cell-Summary");
        rf.setHorizontalAlignment(StringRendererFactory.ALIGN_RIGHT);
      }

      int vc = 0;
      for (int col = 0; col < columnDefs.size(); ++col) {
        DxGridColumnDef cd = getColumnDef(col);
        if (!cd.isVisible())
          continue;
        if (row >= oldRowCount + footerRowCount) {
          rf.render(this, row, vc, summaryProvider.getData(col), cd.getWidth(), true, false);
        } else {
          CellRenderer cr = (CellRenderer) getWidget(row, vc);
          cr.setValue(summaryProvider.getData(col));
        }
        vc++;
      }
    }
    fireDxGridChange(this);
  }

  public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
    if (readOnly) {
      selectRow(row);
    } else {
      activateEditor(row, cell);
    }
  }

  public void onKeyDown(Widget sender, char keyCode, int modifiers) {
    Coordinate coor;
    // if (sender == currentEditor) {
    if (sender == this && !isReadOnly() && modifiers == 0) {
      switch (keyCode) {
      case KEY_ENTER: {
        coor = getNextEnterStop();
        if (coor != null) {
          activateEditor(coor.row, coor.col);
          cancelKey();
        } else {
          setKey((char) KEY_TAB);
        }
        break;
      }
      case KEY_LEFT: {
        if (isColumnReadOnly(getColumnModelIndex(currentCol)) || currentEditor.isLeftKeyToLeave()) {
          coor = getPriorCol();
          if (coor != null) {
            activateEditor(coor.row, coor.col);
            cancelKey();
          }
        }
      }
        break;
      case KEY_UP: {
        coor = getPriorRow();
        if (coor != null) {
          activateEditor(coor.row, coor.col);
          cancelKey();
        }
        break;
      }
      case KEY_RIGHT: {
        if (isColumnReadOnly(getColumnModelIndex(currentCol)) || currentEditor.isRightKeyToLeave()) {
          coor = getNextCol();
          if (coor != null) {
            activateEditor(coor.row, coor.col);
            cancelKey();
          }
        }
        break;
      }
      case KEY_DOWN: {
        coor = getNextRow();
        if (coor != null) {
          activateEditor(coor.row, coor.col);
          cancelKey();
        }
        break;
      }
      }
    } else if (sender == this && isReadOnly() && modifiers == 0) {
      switch (keyCode) {
      case KEY_UP: {
        coor = getPriorRow();
        if (coor != null) {
          selectRow(coor.row);
        }
        cancelKey();
        break;
      }
      case KEY_DOWN: {
        coor = getNextRow();
        if (coor != null) {
          selectRow(coor.row);
        }
        cancelKey();
        break;
      }
      case KEY_HOME: {
        selectRow(headerRowCount);
        cancelKey();
        break;
      }
      case KEY_END: {
        selectRow(getRowCount() - footerRowCount - 1);
        cancelKey();
        break;
      }
      default: {
        for (int row = headerRowCount; row < getRowCount() - footerRowCount; ++row) {
          if (provider.getData(row, 0).startsWith(new String(new char[] {
            keyCode }))) {
            selectRow(row);
            cancelKey();
            break;
          }
        }
      }
      }
    }
  }

  public void onKeyPress(Widget sender, char keyCode, int modifiers) {
  }

  public void onKeyUp(Widget sender, char keyCode, int modifiers) {
  }

  public void onClick(Widget sender) {
  }

  private void selectRow(int newRow) {
    if (newRow < headerRowCount || newRow >= getRowCount() - footerRowCount)
      return;

    if (newRow != currentRow && currentRow >= headerRowCount
        && currentRow < getRowCount() - footerRowCount && currentRow != -1) {
      getRowFormatter().setStyleName(currentRow, rowStyleName);
      for (int col = 0; col < getColumnCount(); ++col) {
        getWidget(currentRow, col).setStyleName(
            getCellRendererFactory(getColumnModelIndex(col)).getStyleName());
      }
    }
    currentRow = newRow;
    getRowFormatter().setStyleName(currentRow, selectedRowStyleName);
    for (int col = 0; col < getColumnCount(); ++col) {
      getWidget(currentRow, col).setStyleName(
          getCellRendererFactory(getColumnModelIndex(col)).getSelectedStyleName());
    }
  }

  /**
   * @param newRow
   * @param newCol
   *            grid column index, not column model index
   */
  public void activateEditor(int newRow, int newCol) {
    // the same cell, editor already activated
    if (newRow == currentRow && newCol == currentCol || newRow < headerRowCount
        || newRow >= getRowCount() - footerRowCount)
      return;

    // restore currently editing cell
    if (currentRow != -1 && currentCol != -1 && currentRow < getRowCount() - footerRowCount) {
      DxGridColumnDef cd = getColumnDef(getColumnModelIndex(currentCol));
      if (cd.isReadOnly()) {
        if (currentDisplayer != null) {
          currentDisplayer.setStyleName(currentDisplayerStyleName);
        }
      } else if (currentEditor != null) {
        currentEditor.setFocus(false);
        currentDisplayer.setValue(currentEditor.getValue() == null ? null : currentEditor
            .getValue().toString());
        setWidget(currentRow, currentCol, (Widget) currentDisplayer);
      }
    }

    // set current row and col
    currentRow = newRow;
    currentCol = newCol;

    currentDisplayer = (CellRenderer) getWidget(currentRow, currentCol);
    DxGridColumnDef cd = getColumnDef(getColumnModelIndex(currentCol));
    if (cd.isReadOnly()) {
      if (currentDisplayer != null) {
        currentDisplayerStyleName = currentDisplayer.getStyleName();
        currentDisplayer.setStyleName("DxGrid-Cell-Focused");
      }
    } else {
      // activate editor
      currentEditor = getEditor(getColumnModelIndex(currentCol));
      currentEditor.activate(this, currentRow, currentCol,
          currentDisplayer.getValue() == null ? null : currentDisplayer.getValue().toString(),
          ((Widget) currentDisplayer).getOffsetWidth() + "px", ((Widget) currentDisplayer)
              .getOffsetHeight()
              + "px", isCellReadOnly(currentRow, currentCol));
    }
  }

  public void addColumnDef(DxGridColumnDef colDef) {
    columnDefs.add(colDef);
  }

  public DxGridColumnDef getColumnDef(int columnModelIndex) {
    return (DxGridColumnDef) columnDefs.get(columnModelIndex);

  }

  public DxGridDataProvider getProvider() {
    return provider;
  }

  public int getCurrentRow() {
    return currentRow;
  }

  public int getCurrentCol() {
    return currentCol;
  }

  private static final FocusImpl impl = FocusImpl.getFocusImplForWidget();

  public void setFocus(boolean focus) {
    if (focus) {
      impl.focus(getElement());
    } else {
      impl.blur(getElement());
    }
  }

  public void setProvider(DxGridDataProvider provider) {
    this.provider = provider;
    setHasSummaryRow(hasSummaryRow);
  }

  public boolean isReadOnly() {
    return readOnly;
  }

  public void setReadOnly(boolean readOnly) {
    this.readOnly = readOnly;
  }

  public String getRowStyleName() {
    return rowStyleName;
  }

  public void setRowStyleName(String rowStyleName) {
    this.rowStyleName = rowStyleName;
  }

  public String getSelectedRowStyleName() {
    return selectedRowStyleName;
  }

  public void setSelectedRowStyleName(String selectedRowStyleName) {
    this.selectedRowStyleName = selectedRowStyleName;
  }

  public int getHeaderRowCount() {
    return headerRowCount;
  }

  public void setHeaderRowCount(int headerRowCount) {
    this.headerRowCount = headerRowCount;
  }

  public int getFooterRowCount() {
    return footerRowCount;
  }

  public boolean hasSummaryRow() {
    return hasSummaryRow;
  }

  public void setHasSummaryRow(boolean hasSummaryRow) {
    this.hasSummaryRow = hasSummaryRow;
    if (hasSummaryRow) {
      footerRowCount = 1;
    } else {
      footerRowCount = 0;
    }
  }

  public boolean isCompact() {
    return compact;
  }

  public void setCompact(boolean compact) {
    this.compact = compact;
    if (compact) {
      headerRowCount = 0;
      rowStyleName = "DxGrid-Compact-Row";
      selectedRowStyleName = "DxGrid-Compact-Row-Selected";
      summaryRowStyleName = "DXGrid-Row-Summary";
    } else {
      headerRowCount = 1;
      rowStyleName = "DxGrid-Row";
      selectedRowStyleName = "DxGrid-Row-Selected";
      summaryRowStyleName = "DXGrid-Row-Summary";
    }
  }

  public int getBlankRowCount() {
    return blankRowCount;
  }

  public void setBlankRowCount(int blankRowCount) {
    this.blankRowCount = blankRowCount;
  }

  public String getValue(int row, int col) {
    Widget w = (Widget) getWidget(row, col);
    if (w instanceof CellRenderer)
      return formatToString(((CellRenderer) w).getValue());
    else
      return formatToString(((CellEditor) w).getValue());
  }

  private String formatToString(Object obj) {
    if (obj == null)
      return null;
    else
      return obj.toString();
  }

  public void setValue(int row, int col, String value) {
    setValue(row, col, value, false);
  }

  private void setValue(int row, int col, String value, boolean removeWidget) {
    Widget w = (Widget) getWidget(row, col);
    if (removeWidget) {
      remove(w);
    } else {
      if (w instanceof CellRenderer)
        ((CellRenderer) w).setValue(value);
      else
        ((CellEditor) w).setValue(value);
    }
  }

  public void refreshCell(int row, int col) {
    int providerRow = row - headerRowCount;
    int providerCol = getColumnModelIndex(col);
    String value = "";
    boolean removeWidget = false;
    if (row < getRowCount() - footerRowCount) {
      if (providerRow < provider.getRowCount()) {
        value = provider.getData(providerRow, providerCol);
      } else {
        removeWidget = true;
      }
    } else {
      value = summaryProvider.getData(providerCol);
    }
    setValue(row, col, value, removeWidget);
  }

  public void refreshRow(int row) {
    for (int col = 0; col < getColumnCount(); ++col)
      refreshCell(row, col);
  }

  private Coordinate getPriorRow() {
    if (currentRow > headerRowCount)
      return new Coordinate(currentRow - 1, currentCol);
    return null;
  }

  private Coordinate getNextRow() {
    if (currentRow < getRowCount() - footerRowCount - 1)
      return new Coordinate(currentRow + 1, currentCol);
    return null;
  }

  private Coordinate getPriorCol() {
    for (int col = currentCol - 1; col >= 0; --col) {
      DxGridColumnDef cd = getColumnDef(getColumnModelIndex(col));
      if (!cd.isReadOnly())
        return new Coordinate(currentRow, col);
    }
    return null;
  }

  private Coordinate getNextCol() {
    for (int col = currentCol + 1; col < getColumnCount(); ++col) {
      DxGridColumnDef cd = getColumnDef(getColumnModelIndex(col));
      if (!cd.isReadOnly())
        return new Coordinate(currentRow, col);
    }
    return null;
  }

  private Coordinate getNextEnterStop() {
    for (int col = currentCol + 1; col < getColumnCount(); ++col) {
      DxGridColumnDef cd = getColumnDef(getColumnModelIndex(col));
      if (cd.isEnterStop() && !cd.isReadOnly())
        return new Coordinate(currentRow, col);
    }
    if (currentRow < getRowCount() - footerRowCount - 1) {
      int row = currentRow + 1;
      for (int col = 0; col <= currentCol; ++col) {
        DxGridColumnDef cd = getColumnDef(getColumnModelIndex(col));
        if (cd.isEnterStop() && !cd.isReadOnly())
          return new Coordinate(row, col);
      }
    }
    return null;
  }

  private CellRendererFactory getCellRendererFactory(int columnModelIndex) {
    return getColumnDef(columnModelIndex).getRendererFactory();
  }

  private boolean isColumnReadOnly(int columnModelIndex) {
    return getColumnDef(columnModelIndex).isReadOnly();
  }

  private CellEditor getEditor(int columnModelIndex) {
    return getColumnDef(columnModelIndex).getEditor();
  }

  HashMap readOnlyCells = new HashMap();

  private String getCellId(int row, int col) {
    return row + "," + col;
  }

  public void setCellReadOnly(int row, int col, boolean readOnly) {
    if (getColumnDef(getColumnModelIndex(col)).isReadOnly())
      return;
    String key = getCellId(row, col);
    if (readOnly)
      readOnlyCells.put(key, key);
    else
      readOnlyCells.remove(key);
  }

  private boolean isCellReadOnly(int row, int col) {
    if (isColumnReadOnly(getColumnModelIndex(col)))
      return true;
    return readOnlyCells.containsKey(getCellId(row, col));
  }

  public class Coordinate {
    int row;
    int col;

    public Coordinate(int row, int col) {
      this.row = row;
      this.col = col;
    }
  }

  protected void onLoad() {
    // clear();
  }

  protected void onUnload() {
    // columnDefs = null;
    // provider = null;
    // currentDisplayer = null;
    // currentEditor = null;
  }

  public void onBrowserEvent(Event event) {
    super.onBrowserEvent(event);
    int type = DOM.eventGetType(event);
    if ((keyboardListeners != null) && (type & Event.KEYEVENTS) != 0) {
      // Fire the keyboard event. Hang on to the current event object so that
      // cancelKey() and setKey() can be implemented.
      currentEvent = event;
      keyboardListeners.fireKeyboardEvent(this, event);
      currentEvent = null;
    } else if (type == Event.ONDBLCLICK) {
      if (dxGridListeners != null) {
        // Find out which cell was actually clicked.
        Element td = getEventTargetCell(event);
        if (td == null) {
          return;
        }
        Element tr = DOM.getParent(td);
        Element body = DOM.getParent(tr);
        int row = DOM.getChildIndex(body, tr);
        int column = DOM.getChildIndex(tr, td);
        // Fire the event.
        fireCellDblClicked(this, row, column);
      }
    }
  }

  KeyboardListenerCollection keyboardListeners = null;

  public void addKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners == null)
      keyboardListeners = new KeyboardListenerCollection();
    keyboardListeners.add(listener);
  }

  public void removeKeyboardListener(KeyboardListener listener) {
    keyboardListeners.remove(listener);
  }

  public void cancelKey() {
    if (currentEvent != null) {
      DOM.eventPreventDefault(currentEvent);
    }
  }

  public void setKey(char key) {
    if (currentEvent != null) {
      DOM.eventSetKeyCode(currentEvent, key);
    }
  }

  ArrayList cellWidgetClickListeners = new ArrayList();

  public void addCellWidgetClickListener(CellWidgetClickListener listener) {
    cellWidgetClickListeners.add(listener);
  }

  public void removeCellWidgetClickListener(CellWidgetClickListener listener) {
    cellWidgetClickListeners.remove(listener);
  }

  public void fireCellWidgetClick(DxGrid theGrid, Widget sender, int row, int col) {
    for (Iterator it = cellWidgetClickListeners.iterator(); it.hasNext();) {
      CellWidgetClickListener listener = (CellWidgetClickListener) it.next();
      listener.onCellWidgetClick(theGrid, sender, row, col);
    }
  }

  ArrayList dxGridListeners = new ArrayList();

  public void addDxGridListener(DxGridListener listener) {
    dxGridListeners.add(listener);
  }

  public void removeDxGridListener(DxGridListener listener) {
    dxGridListeners.remove(listener);
  }

  public void fireDxGridChange(DxGrid theGrid) {
    for (Iterator it = dxGridListeners.iterator(); it.hasNext();) {
      DxGridListener listener = (DxGridListener) it.next();
      listener.afterRefresh(theGrid);
    }
  }

  public void fireCellDblClicked(DxGrid theGrid, int row, int column) {
    for (Iterator it = dxGridListeners.iterator(); it.hasNext();) {
      DxGridListener listener = (DxGridListener) it.next();
      listener.onCellDblClick(theGrid, row, column);
    }
  }
}
